package org.daochong.lang;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class Resource {

	private static Map<String, List<Resource>> FILES_CACHE;
	private static Map<String, List<Resource>> FILES_PATH_CACHE;
	private static Map<String, List<Resource>> FOLDERS_CACHE;
	private static List<File> SOURCES;

	private static void init() {
		if (FILES_CACHE != null)
			return;
		FILES_CACHE = new HashMap<String, List<Resource>>();
		FOLDERS_CACHE = new HashMap<String, List<Resource>>();
		FILES_PATH_CACHE = new HashMap<String, List<Resource>>();
		SOURCES = new ArrayList<File>();
		// Load java.class.path
		String str = System.getProperty("java.class.path");
		for (String fd : str.split(";")) {
			File file = new File(fd);
			SOURCES.add(file);
		}
		// 加载Web状态下的资源文件
		URL url = Resource.class.getProtectionDomain().getCodeSource().getLocation();
		String path = url.getFile();
		File f = new File(path);
		if (path.endsWith(".class")) {
			path = path.substring(0, path.length() - 7 - Resource.class.getName().length());
			SOURCES.add(f);
			f = new File(f.getParent() + "/lib");
			if (f.exists()) {
				loadLibary(f);
			}
		} else if (f.isDirectory()) {
			SOURCES.add(f);
			f = new File(f.getParent() + "/lib");
			if (f.exists()) {
				loadLibary(f);
			}
		} else if (path.endsWith(".jar") || path.endsWith(".zip")) {
			loadLibary(f.getParentFile());
			File fc = new File(f.getParentFile().getParentFile(), "classes");
			if (fc.exists()) {
				SOURCES.add(fc);
			}
		}
		loadSource();
	}

	private static void loadSource() {
		for (File file : SOURCES) {
			loadSource(file);
		}
	}

	private static void loadSource(File file) {
		if (file != null) {
			if (file.isDirectory()) {
				loadSourceFolder(file, file.getPath());
			} else {
				loadLib(file, file.getPath());
			}
		}
	}

	private static void loadLib(File file, String base) {
		if (file == null)
			return;
		String name = file.getName();
		boolean isJar = name.endsWith(".jar") || name.endsWith(".zip");
		if (!file.isFile() || !isJar)
			return;
		if (isJar) {
			try {
				JarFile jf = new JarFile(file);
				Enumeration<JarEntry> en = jf.entries();
				while (en.hasMoreElements()) {
					JarEntry je = en.nextElement();
					if (je.isDirectory() || je.getName().endsWith(".class"))
						continue;
					add("/" + je.getName(), file.getPath() + "!" + je.getName(), je.getSize());
				}
				jf.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private static void loadSourceFolder(File file, String base) {
		for (File f : file.listFiles()) {
			if (f.isFile()) {
				if (!f.getName().endsWith(".class"))
					add(f.getPath().substring(base.length()).replaceAll("\\\\", "/"), f.getPath(), f.length());
			} else {
				loadSourceFolder(f, base);
			}
		}
	}

	private static void add(String contextPath, String filePath, long contentLength) {
		int pos = filePath.lastIndexOf("/");
		if (pos == -1) {
			pos = filePath.lastIndexOf("\\");
		}
		String name = filePath.substring(pos + 1);
		List<Resource> list = FILES_PATH_CACHE.get(contextPath);
		if (list == null) {
			list = new ArrayList<Resource>();
			FILES_PATH_CACHE.put(contextPath, list);
		}
		Resource r = new Resource(name, filePath, contentLength);
		r.setContextPath(contextPath);
		list.add(r);
		if (contextPath.equals("/")) {
			list = FOLDERS_CACHE.get("/");
			if (list == null) {
				list = new ArrayList<Resource>();
				FOLDERS_CACHE.put("/", list);
			}
			list.add(r);
			return;
		}

		list = FILES_CACHE.get(name);
		if (list == null) {
			list = new ArrayList<Resource>();
			FILES_CACHE.put(name, list);
		}
		list.add(r);
		if (!contextPath.startsWith("/"))
			contextPath = "/" + contextPath;
		String last = "";
		String[] ar = contextPath.split("/");
		for (int i = 1; i < ar.length - 1; i++) {
			last += "/" + ar[i];
			list = FOLDERS_CACHE.get(last);
			if (list == null) {
				list = new ArrayList<Resource>();
				FOLDERS_CACHE.put(last, list);
			}
			list.add(r);
		}
	}

	private static void loadLibary(File f) {
		File[] fs = f.listFiles();
		if (fs == null) {
			System.out.println("NF:" + f.exists());
			System.out.println("NF:" + f.getPath());
			return;
		}
		for (File file : fs) {
			if (file.isDirectory()) {
				loadLibary(file);
			} else {
				if (file.getName().endsWith(".jar")) {
					SOURCES.add(file);
				} else if (file.getName().endsWith(".zip")) {
					SOURCES.add(file);
				}
			}
		}
	}

	public static void addResource(String path) {
		File file = new File(path);
		if (file.exists()) {
			SOURCES.add(file);
			loadSource(file);
		}
	}

	public static void addLib(String path) {
		File file = new File(path);
		if (file.exists()) {
			if (file.isFile() && (path.endsWith(".jar") || path.endsWith(".zip"))) {
				SOURCES.add(file);
				loadSource(file);
			} else {
				for (File f : file.listFiles()) {
					addLib(f.getPath());
				}
			}
		}
	}

	public static List<Resource> getResources(String path) {
		init();
		List<Resource> list = new ArrayList<Resource>();
		if (FILES_PATH_CACHE.containsKey(path)) {
			List<Resource> ls = FILES_PATH_CACHE.get(path);
			boolean find = false;
			for (Resource resource : ls) {
				if (!resource.isJar()) {
					find = true;
					list.add(resource);
				}
			}
			if (!find) {
				list.addAll(ls);
			}
		} else {
			for (String key : FILES_PATH_CACHE.keySet()) {
				if (StringUtils.pathMatcher(path, key)) {
					List<Resource> ls = FILES_PATH_CACHE.get(key);
					if (ls.size() == 1) {
						list.add(ls.get(0));
					} else {
						boolean find = false;
						for (Resource resource : ls) {
							if (!resource.isJar()) {
								find = true;
								list.add(resource);
								break;
							}
						}
						if (!find) {
							list.addAll(ls);
						}
					}
				}
			}
		}
		return list;
	}

	public static Resource getResource(String path) {
		List<Resource> list = getResources(path);
		if (list == null || list.size() == 0)
			return null;
		for (Resource r : list) {
			if (!r.isJar()) {
				return r;
			}
		}
		return list.get(0);

	}

	public static List<Resource> getResourcesByName(String name) {
		init();
		return FILES_CACHE.get(name);
	}

	public static Resource getResourceByName(String name) {
		init();
		List<Resource> list = getResourcesByName(name);
		if (list == null || list.size() == 0)
			return null;
		for (Resource r : list) {
			if (!r.isJar()) {
				return r;
			}
		}
		return list.get(0);
	}

	public static List<Resource> getResourcesByFolder(String folder) {
		init();
		if (StringUtils.isEmpty(folder)) {
			return null;
		}
		if (FOLDERS_CACHE.containsKey(folder)) {
			List<Resource> list = FOLDERS_CACHE.get(folder);
			Collections.sort(list, new ResourceComparator());
			return list;
		} else {
			return new ArrayList<Resource>(0);
		}
	}

	private String source;
	private String fileName;
	private boolean jar = false;
	private File sourceFile;
	private String entryPath;
	private long contentLength = -1;
	private String contextPath;

	public String getContextPath() {
		return contextPath;
	}

	public void setContextPath(String contextPath) {
		this.contextPath = contextPath;
	}

	public Resource(String fileName, String source) {
		this.source = source;
		this.fileName = fileName;
		load();
	}

	public Resource(String fileName, String source, long contentLength) {
		this.source = source;
		this.fileName = fileName;
		this.contentLength = contentLength;
		load();
	}

	private void load() {
		int pos = this.source.indexOf("!");
		if (pos == -1) {
			this.sourceFile = new File(this.source);
		} else {
			jar = true;
			this.sourceFile = new File(this.source.substring(0, pos));
			this.entryPath = this.source.substring(pos + 1);
		}
	}

	public File getSourceFile() {
		return this.sourceFile;
	}

	public String getSource() {
		return source;
	}

	public InputStream getInputStream() throws IOException {
		if (!this.jar) {
			return new FileInputStream(this.sourceFile);
		}
		return new JarEntryInputStream(this.sourceFile, this.entryPath);
	}

	public String getFileName() {
		return fileName;
	}

	public boolean isJar() {
		return jar;
	}

	public String toString() {
		return this.getClass().getName() + ":" + getSource();
	}

	public long contentLength() {
		if (contentLength == -1) {
			return this.sourceFile.length();
		}
		return this.contentLength;
	}

	public long lastModified() {
		return this.sourceFile.lastModified();
	}

}

class ResourceComparator implements Comparator<Resource> {

	public int compare(Resource source, Resource target) {
		if (!source.isJar()) {
			return 1;
		}
		if (!target.isJar()) {
			return -1;
		}
		return 0;
	}

}

class JarEntryInputStream extends InputStream {
	private InputStream in;
	private JarFile jf;

	public JarEntryInputStream(File file, String path) {
		try {
			jf = new JarFile(file);
			this.in = jf.getInputStream(jf.getJarEntry(path));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	
	public int read() throws IOException {
		return this.in.read();
	}

	
	public int read(byte[] b) throws IOException {
		return this.in.read(b);
	}

	
	public int read(byte[] b, int off, int len) throws IOException {
		return this.in.read(b, off, len);
	}

	
	public void close() throws IOException {
		jf.close();
		super.close();
	}

}
